package  
{
	import org.flixel.FlxG;
	import org.flixel.FlxPoint;
	/**
	 * ...
	 * @author morgan
	 */
	public class Player extends ReflectionSprite
	{
		[Embed (source = "../data/player.png")] private var img_player:Class;
		
		// Movement consts
		public const WALKSPEED:Number = 100;
		public const WALKACCEL:Number = 1000;
		public const STOPSPEED:Number = 800;
		public const JUMPFORCE:Number = -170;
		public const JUMPSTOPFORCE:Number = 150;
		public const GRAVITY:Number = 275;
		public const AIRCONTROL:Number = 1000;
		public const AIRSTOPSPEED:Number = 800;
		public const BOUNCEFORCE:Number = -190;
		
		// State machine is just a first-class update function
		public var stateFunc:Function = function():void{};
		
		
		// Platformer movement timers, helpers, etc
		public var tryingToJumpTimer:Number = 0;
		public var tryingToJump:Boolean = false;
		
		public var touchingGroundTimer:Number = 0;
		public var touchingGround:Boolean = false;
		
		
		// Throwing
		public var heldObject:Throwable = null;
		public const GRABRANGE:Number = 14;
		public const THROWSPEEDX:Number = 140;
		public const THROWSPEEDY:Number = -30
		
		public var heldString:String = ""; // Animation name has this postfixed
		
		// Inputs
		public var input_left:Boolean = false;
		public var input_jump:Boolean = false;		
		public var input_just_jump:Boolean = false;
		public var input_right:Boolean = false;
		public var input_throw:Boolean = false;
		public var input_just_throw:Boolean = false;
		
		
		// Recording
		public var saveRec:PlayerRecording;
		public var saveRecTime:Number = 0;
		public var readRec:PlayerRecording;
		public var readRecTime:Number = 0;
		public var playback:Boolean = false;
		
		// Other game logic
		// End trigger
		public var awaitTheEnd:Boolean = false;
		
		// Pushing blocks
		public var pushTimer:Number = 0;
		
		public function Player(x:int, y:int, scene:GameScene) 
		{
			super(x, y, scene);
			loadGraphic(img_player, true, true, 16, 16);
			addAnimation("walk", [0, 1, 2, 1, 3, 4, 5, 4], 10);
			addAnimation("idle", [6, 6, 6, 7, 8, 8, 8, 9, 8, 8, 8, 8, 8, 8, 9, 8, 8, 8, 8, 8, 8, 10, 10, 11, 11, 10, 10, 11, 11, 10, 10, 11, 11, 10, 10, 8, 8, 8, 8, 8, 8, 9, 8, 8, 8, 7, 6, 6, 6, 6], 4);
			addAnimation("jump", [12, 13, 14, 14, 14, 14, 14, 14, 14, 14], 3, false);			
			addAnimation("walk_held", [15, 16, 17, 16, 18, 19, 20, 19], 10);
			addAnimation("idle_held", [6, 6, 6, 7, 8, 8, 8, 9, 8, 8, 8, 8, 8, 8, 9, 8, 8, 8, 8, 8, 8, 10, 10, 11, 11, 10, 10, 11, 11, 10, 10, 11, 11, 10, 10, 8, 8, 8, 8, 8, 8, 9, 8, 8, 8, 7, 6, 6, 6, 6], 4);
			addAnimation("jump_held", [12, 13, 21, 21, 21, 21, 21, 21, 21, 21], 3, false);
			
			acceleration.y = GRAVITY;
			stateFunc = this.stateGround;
			
			width = 12;
			offset.x = 2;
			height = 15;
			offset.y = 1;
			
			readRec = new PlayerRecording();
			saveRec = new PlayerRecording();
		}
		
		override public function update():void 
		{
			super.update();
			
			// If you've hit the end trigger:
			if (awaitTheEnd)
			{
				velocity.x *= 0.9;
				return;
			}
			
			// This way we can abstract the input such that a recording can control the player by faking inputs
			if (playback)
			{
				updateRecordingInput();
			}
			else
			{
				updateKeyboardInput();
			}
			
			// Put a timer on the jump input so that you can press the button just before landing and it still works.
			tryingToJumpTimer -= FlxG.elapsed;
			tryingToJump = tryingToJumpTimer > 0;
			if (input_just_jump)
			{
				tryingToJump = true;
				tryingToJumpTimer = 0.25;
			}
			
			// A timer for touching the ground as well, so if you fall off a ledge you have a short time to jump.
			touchingGroundTimer -= FlxG.elapsed;
			touchingGround = touchingGroundTimer > 0;			
			if (isTouching(FLOOR))
			{
				touchingGround = true;
				touchingGroundTimer = 0.1;
			}
			
			pushTimer = Math.max(0, pushTimer - FlxG.elapsed / 2);
			
			updateGrab();
			
			stateFunc();
			//debugState();
		}
		
		override public function postUpdate():void 
		{
			super.postUpdate();
			
			// Position the object you're holding in postUpdate so it doesn't jitter around (update order and all that)
			if (heldObject)
			{
				heldObject.x = x + width / 2 - heldObject.width / 2 + (facing == RIGHT ? 9 : -9);
				heldObject.y = y + height / 2 - heldObject.height / 2;				
			}
		}
		
		public function updateGrab():void
		{
			scene.pickupObjectUI.visible = false;
			if (heldObject == null)
			{
				// Picking up throwable objects
				for (var i:int = 0; i < scene.throwables.length; i++)
				{
					var obj:Throwable = scene.throwables.members[i] as Throwable;
					if (!obj) { continue; }
					// Find out if it's in grab range
					var dx:Number = (x + width / 2) - (obj.x + obj.width / 2);
					var dy:Number = (y + height / 2) - (obj.y + obj.height / 2);
					if (dx * dx + dy * dy < GRABRANGE * GRABRANGE)
					{
						
						scene.pickupObjectUI.visible = true;
						scene.pickupObjectUI.x = obj.x + obj.width / 2 - scene.pickupObjectUI.width / 2;
						scene.pickupObjectUI.y = obj.y - scene.pickupObjectUI.height - 2;
						
						if (input_just_throw)
						{
							heldObject = obj;
							break;
						}
					}
				}	
				heldString = "";
			}
			else 
			{
				heldObject.velocity.x = 0; heldObject.velocity.y = 0;
				if (input_just_throw)
				{
					heldObject.velocity.x = (facing == RIGHT ? THROWSPEEDX : -THROWSPEEDX) / heldObject.mass;
					heldObject.velocity.y = THROWSPEEDY / heldObject.mass;
					heldObject = null;
				}
				heldString = "_held";
			}
		}
		
		public function debugState():void
		{
			if (stateFunc == this.stateGround) { color = 0xffffff; }
			else if (stateFunc == this.stateAir) { color = 0x8888fff; }
		}
		
		// Called every frame you're on the ground.
		public function stateGround():void
		{
			if (tryingToJump > 0)
			{
				velocity.y = JUMPFORCE;
				stateFunc = stateAir;
				touchingGround = false;
				touchingGroundTimer = 0;
				tryingToJump = false;
				tryingToJumpTimer = 0;
			}
			
			if (!touchingGround)
			{
				stateFunc = this.stateAir;			
			}
			
			if (input_left)
			{
				velocity.x = Math.max(velocity.x - WALKACCEL * FlxG.elapsed, -WALKSPEED);
				facing = LEFT;
				play("walk" + heldString);
			}
			else if (input_right)
			{
				velocity.x = Math.min(velocity.x + WALKACCEL * FlxG.elapsed, WALKSPEED);
				facing = RIGHT;
				play("walk" + heldString);
			}
			else
			{
				var sign:Number = velocity.x > 0 ? 1 : -1;
				velocity.x -= Math.min(sign * velocity.x, STOPSPEED * FlxG.elapsed) * sign;
				play("idle" + heldString);
			}
		}
		
		// Called every frame you are in the air.
		public function stateAir():void
		{
			if (!input_jump && velocity.y < 40)
			{
				velocity.y += Math.min(Math.abs(velocity.y), JUMPSTOPFORCE * FlxG.elapsed);
			}
			
			if (input_left)
			{
				velocity.x = Math.max(velocity.x - AIRCONTROL * FlxG.elapsed, -WALKSPEED);
				facing = LEFT;
			}
			else if (input_right)
			{
				velocity.x = Math.min(velocity.x + AIRCONTROL * FlxG.elapsed, WALKSPEED);
				facing = RIGHT;
			}
			else
			{
				var sign:Number = velocity.x > 0 ? 1 : -1;
				velocity.x -= Math.min(sign * velocity.x, AIRSTOPSPEED * FlxG.elapsed) * sign;
			}
			play("jump" + heldString);
			
			if (touchingGround)
			{
				stateFunc = this.stateGround;
			}
		}
		
		public function bounceOffEnemy():void
		{
			velocity.y = BOUNCEFORCE;
		}
		
		public function updateKeyboardInput():void
		{
			input_jump = FlxG.keys.X;
			input_just_jump = FlxG.keys.justPressed("X");
			input_throw = FlxG.keys.C;
			input_just_throw = FlxG.keys.justPressed("C");
			input_left = FlxG.keys.LEFT;
			input_right = FlxG.keys.RIGHT;
			
			
			// Save a recording of inputs
			if ( FlxG.keys.justPressed("LEFT") ) { saveRec.addInput(PlayerRecording.EVT_LEFT_DOWN, saveRecTime); }
			if ( FlxG.keys.justReleased("LEFT") ) { saveRec.addInput(PlayerRecording.EVT_LEFT_UP, saveRecTime); }
			if ( FlxG.keys.justPressed("RIGHT") ) { saveRec.addInput(PlayerRecording.EVT_RIGHT_DOWN, saveRecTime); }
			if ( FlxG.keys.justReleased("RIGHT") ) { saveRec.addInput(PlayerRecording.EVT_RIGHT_UP, saveRecTime); }
			if ( FlxG.keys.justPressed("X") ) { saveRec.addInput(PlayerRecording.EVT_JUMP_DOWN, saveRecTime); }
			if ( FlxG.keys.justReleased("X") ) { saveRec.addInput(PlayerRecording.EVT_JUMP_UP, saveRecTime); }
			if ( FlxG.keys.justPressed("C") ) { saveRec.addInput(PlayerRecording.EVT_THROW_DOWN, saveRecTime); }
			if ( FlxG.keys.justReleased("C") ) { saveRec.addInput(PlayerRecording.EVT_THROW_UP, saveRecTime); }
			
			saveRecTime += FlxG.elapsed;
		}
		
		public function setPlaybackMode(rec:String):void
		{
			playback = true;
			readRec.loadRecordString(rec);
		}
		
		override public function collides(o:ReflectionSprite):void 
		{
			super.collides(o);
		}
		
		public function updateRecordingInput():void
		{
			readRecTime += FlxG.elapsed;
			var events:Array = readRec.getUntil(readRecTime);
			input_just_jump = false;
			input_just_throw = false;
			for each (var event:uint in events)
			{
				switch(event)
				{
					case PlayerRecording.EVT_LEFT_DOWN:
						input_left = true; break;
					case PlayerRecording.EVT_LEFT_UP:
						input_left = false; break;
					case PlayerRecording.EVT_RIGHT_DOWN:
						input_right = true; break;
					case PlayerRecording.EVT_RIGHT_UP:
						input_right = false; break;
					case PlayerRecording.EVT_JUMP_DOWN:
						input_jump = true; input_just_jump = true; break;
					case PlayerRecording.EVT_JUMP_UP:
						input_jump = false; break;
					case PlayerRecording.EVT_THROW_DOWN:
						input_throw = true; input_just_throw = true; break;
					case PlayerRecording.EVT_THROW_UP:
						input_throw = false; break;
				}
			}
		}
	}

}